/*
*    Phantom v2.0 GUI Automation Software
*    Copyright (C) 2009  John C. Van Arsdall (john@phantomtest.com)
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License along
*    with this program; if not, write to the Free Software Foundation, Inc.,
*    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include ".\ssi_socketmanager.h"


#define  SSI_OPEN_REQUEST      131
#define  SSI_LINE_REQUEST      132
#define  SSI_RESPONSE_OUTPUT   133
#define  SSI_COMMAND           134
#define  SSI_BYE               135

#define  SSI_RSP_PORT          200


// OK, so the flow goes something like this:
// Init
//   Open up main listener, that stays open for the duration (L1)
// GetConnection
//   Listener accepts connection (C1)
//   New connection C1 gets open request
// OpenConnection
//   New listener opened to listen on port reflecting open request (L2)
//   Port of new listener sent on original C1 connection 
//   Ack read on original C1 connection
//   L2 accepts connection used for script running (C2)
//   L2 is closed, since it is no longer needed
// GetConnection
//   ** SHOULD CLOSE C1 so ONLY L1 REMAINS OPEN **
//   Creates socket containing C2 and returns it


CSocketManager::CSocketManager(void)
{
	m_Connection = 0;
	m_Listener = 0;
	m_MinPort = 5000;
	m_MaxPort = 6000;
}

CSocketManager::~CSocketManager(void)
{
	//if(m_Connection){
	//	CSocketSSI::Disconnect(m_Connection);
	//}
	//if(m_Listener){
	//	CSocketSSI::Disconnect(m_Listener);
	//}
}

CSocketSSI* CSocketManager::GetConnection()
{
	//if(m_Connection){
	//	CSocketSSI::Disconnect(m_Connection);
	//	m_Connection = 0;
	//}
	if(m_Listener == 0){
		return 0;
	}

	SSI_INT64 Port = 0;
	//Log(">>>> Waiting for a connection <<<<");

	SSI_VERBOSE_LOG(">>>> Waiting for a connection <<<<\n");
	m_Connection = CSocketSSI::AcceptConnection(m_Listener, Port);
  SSI_VERBOSE_LOG("ACCEPTING C1");
	m_Socket.myHandle = m_Connection;

	if (m_Connection != INVALID_SOCKET) {
		string s = m_Socket.IPFromSocket(m_Connection);
		string s2 = "Accepted Connection: ";
		s2 += s;
		//Log(s2.c_str());
	}else{
    SSI_VERBOSE_LOG("C1 INVALID SOCKET");
    return 0;
		//Log("ERROR*** Bad Connection");
	}

	// Check for an open request
  CSockCmd aCmd;
	m_Socket.myHandle = m_Connection;
	if(!aCmd.ReadMe(m_Socket)){
    SSI_VERBOSE_LOG("C1 UNABLE TO READ OPEN REQUEST");
    CSocketSSI::Disconnect(m_Socket.myHandle);
    m_Socket.myHandle = 0;
		return 0;
	}

	if(aCmd.ID != SSI_OPEN_REQUEST){
    SSI_VERBOSE_LOG("C1 READ NON-OPEN REQUEST");
    CSocketSSI::Disconnect(m_Socket.myHandle);
    m_Socket.myHandle = 0;
		return 0;
	}

	// Open a new connection and send it with new socket ID
	SSI_SOCKET newConnection = OpenConnection(Port);
	if(!newConnection){
		SSI_VERBOSE_LOG("NO NEW CONNECTION (NO C2)");
    CSocketSSI::Disconnect(m_Socket.myHandle);
    m_Socket.myHandle = 0;
		return 0;
	}

  SSI_VERBOSE_LOG("CLOSING C1");
  CSocketSSI::Disconnect(m_Socket.myHandle);
  m_Socket.myHandle = 0;

	CSocketSSI* Ret = new CSocketSSI();
	Ret->myHandle = newConnection;
	Ret->myPort = Port;

	return Ret;
}

SSI_SOCKET CSocketManager::OpenConnection(SSI_INT64& Port)
{
	SSI_SOCKET Ret = 0;
	SSI_SOCKET Listener = INVALID_SOCKET;
  SSI_ADDR RemoteAddr = CSocketSSI::LookupAddress(m_Host.c_str());
	if(RemoteAddr == INADDR_NONE){
		printf("ERROR*** Could not resolve address: %s\n", m_Host.c_str());  // KEEP PRINT
		return 0;
	}

	Port = m_MinPort;
  SSI_VERBOSE_LOG("OPENING L2");
	while(Listener == INVALID_SOCKET){
    Listener = CSocketSSI::Listen(RemoteAddr, Port);
		Port++;
		if(Port > m_MaxPort+1){
			printf("ERROR*** Could not connect to: %s\n", m_Host.c_str()); // KEEP PRINT
			return 0;
		}
	}
	Port--;
	//if(Listener == INVALID_SOCKET){
	//	printf("ERROR*** Could not connect to: %s\n", m_Host.c_str()); 
	//	return 0;
	//}

	CSockCmd aCmd;
	aCmd.Set(SSI_RSP_PORT, sizeof(Port), (const char*)(&Port));
	if(!aCmd.SendMe(m_Socket)){
		// Shut down connection
		// Shut down new connection
		return 0;
	}

	if(!aCmd.ReadMe(m_Socket)){
		// Shut down connection
		// Shut down new connection
		return 0;
	}

	if(aCmd.ID != RSP_ACK){
		// Shut down connection
		// Shut down new connection
		return 0;
	}

	printf(">>>> Waiting for a generated connection <<<<\n"); // KEEP PRINT
	Ret = CSocketSSI::AcceptConnection(Listener, Port);
  SSI_VERBOSE_LOG("ACCEPTING C2");
  // DOH!  This will accept the next connection, regardless of the port... Doh!  So, the port is *not* the port counter
	// and the listener port gets disconnected right away... duh...  so the same 'port number' is always used.


  SSI_VERBOSE_LOG("CLOSING L2");
  // Must only be *closed* since not an 'accepted' socket so cant recv
  CSocketSSI::CloseSocket(Listener);
	return Ret;
}

int CSocketManager::Init(SSI_INT64 ListenPort, SSI_INT64 MinPort, SSI_INT64 MaxPort)
{
	//CSocketSSI::socket_log_file = "c:\\ssi_socket_log.log";
	//CSocketSSI::ClearLog();  I Dont think we want to clear the log file anymore

	CSocketSSI::Init();


	char nm[1024];
	gethostname(nm, 1024);
	printf("Host Name: %s (Port: %d)\n", nm, ListenPort); // KEEP PRINT
	char* pcHost = CSocketSSI::IPFromName(nm);


	SSI_ADDR RemoteAddr = CSocketSSI::LookupAddress(pcHost);
	if(RemoteAddr == INADDR_NONE){
		printf("ERROR*** Could not resolve address: %s\n", pcHost); // KEEP PRINT
		return 0;
	}

	// Have to set up the listener, because it will listen for the connection requests
  SSI_VERBOSE_LOG("OPENING L1");
	m_Listener = CSocketSSI::Listen(RemoteAddr, ListenPort);
	if(m_Listener == INVALID_SOCKET){
		printf("ERROR*** Could not connect to: %s\n", pcHost); // KEEP PRINT
		return 0;
	}

	m_Host = pcHost;
	m_MinPort = MinPort;
	m_MaxPort = MaxPort;

	return 1;


}
